package club.dingzm.myapplication.WebSocketUtils;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;


import com.alibaba.fastjson.JSONObject;
import com.lzy.okgo.BuildConfig;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFactory;
import com.neovisionaries.ws.client.WebSocketFrame;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import club.dingzm.myapplication.HttpBreakPoint.BreakpointInfo;
import club.dingzm.myapplication.SPUtil;
import club.dingzm.myapplication.WebScoektDemoActivity;

public class WsUploadFileManager {
    private static WsUploadFileManager mInstance;
    private final String TAG = "WsUploadFileManager";

    /**
     * WebSocket config
     */
    private static final int FRAME_QUEUE_SIZE = 5;
    private static final int CONNECT_TIMEOUT = 5000;
    private static final String DEF_TEST_URL = "ws://192.168.31.253:8111/ddxc/WebSocketUpload/{userId}/{cacheId}";//测试服默认地址
    private static final String DEF_RELEASE_URL = "ws://192.168.31.253:8111/ddxc/WebSocketUpload/{userId}/{cacheId}";//正式服默认地址
    private static final String DEF_URL = BuildConfig.DEBUG ? DEF_TEST_URL : DEF_RELEASE_URL;
    private String url;

    private WsStatus mStatus;
    private WebSocket ws;
    private WsListener mListener;


    private WsUploadFileManager() {
    }

    public static WsUploadFileManager getInstance(){
        if(mInstance == null){
            synchronized (WsUploadFileManager.class){
                if(mInstance == null){
                    mInstance = new WsUploadFileManager();
                }
            }
        }
        return mInstance;
    }

    public void init(String userId,String cacheId){
        try {
            /**
             * configUrl其实是缓存在本地的连接地址
             * 这个缓存本地连接地址是app启动的时候通过http请求去服务端获取的,
             * 每次app启动的时候会拿当前时间与缓存时间比较,超过6小时就再次去服务端获取新的连接地址更新本地缓存
             */
            String configUrl = "";
            url = TextUtils.isEmpty(configUrl) ? DEF_URL : configUrl;
            url = url.replace("{userId}", userId).replace("{cacheId}", cacheId);
            ws = new WebSocketFactory().createSocket(url, CONNECT_TIMEOUT)
                    .setFrameQueueSize(FRAME_QUEUE_SIZE)//设置帧队列最大值为5
                    .setMissingCloseFrameAllowed(false)//设置不允许服务端关闭连接却未发送关闭帧
                    .addListener(this.mListener = new WsListener())//添加回调监听
                    .connectAsynchronously();//异步连接
            setStatus(WsStatus.CONNECTING);
            Log.e(TAG,"初始化连接");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public WebSocket getWs() {
        return ws;
    }

    /**
     * 继承默认的监听空实现WebSocketAdapter,重写我们需要的方法
     * onTextMessage 收到文字信息
     * onConnected 连接成功
     * onConnectError 连接失败
     * onDisconnected 连接关闭
     */
    class WsListener extends WebSocketAdapter {
        @Override
        public void onTextMessage(WebSocket websocket, String text) throws Exception {
            //这里接收到 服务器返回消息
            super.onTextMessage(websocket, text);
            Log.e(TAG, text);
            Result result = JSONObject.parseObject(text, Result.class);
            if(result.isSuccess()){
                if ( "收到更新文件内容".equals(result.getMessage()) ){
                    //未设置 停止时
                    if (!stopFlag){

                        InputStream in = null;
                        try {
                            in = new FileInputStream(file);
                            byte[] bytes = new byte[in.available()];
                            in.read(bytes);
                            in.close();
                            ws.sendBinary(bytes);
                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                        } catch (IOException e) {
                            e.printStackTrace();
                        } finally {
                            if (in != null){
                                try {
                                    in.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }

                    }


                }else if ( "收到文件".equals(result.getMessage()) ){
                    SPUtil.put(context, cacheId, JSONObject.toJSONString(info));
                    System.out.println("第"+info.getCurrentBlock()+"块内容成功返回。");
                    //删除 分片文件缓存
                    if (file.exists()){
                        file.delete();
                    }
                    //未设置 停止时
                    if (!stopFlag){
                        //当不是最后一块文件的时候
                        if (info.getCurrentBlock() != info.getBLOCK_SIZE() ){
                            info.setCurrentBlock( info.getCurrentBlock() +1 ) ;
                            initParms();
                            startSendFile();
                        }else{
                            SPUtil.remove(context,cacheId);
                            info.setSuccess(true);
                            setStop(true);
                            params = new HashMap();
                        }
                    }else{
                        System.out.println("停止发送文件！！！");
                    }

                }else{
                    SPUtil.remove(context,cacheId);
                    info.setSuccess(false);
                    setStop(true);
                    params = new HashMap();
                }
            }else{

                SPUtil.remove(context,cacheId);
                setStop(true);
                params = new HashMap();
                Log.e(TAG, "服务器出现错误: "+text );
            }
        }


        @Override
        public void onConnected(WebSocket websocket, Map<String, List<String>> headers)
                throws Exception {
            super.onConnected(websocket, headers);
            Log.e(TAG, "连接成功");
            setStatus(WsStatus.CONNECT_SUCCESS);
            cancelReconnect();//连接成功的时候取消重连,初始化连接次数
            //重新连接成功之后，继续之前的发送
            if (!stopFlag && params != null  && params.size() != 0 ){
                Log.e(TAG, "连接成功后执行的发送文件！ ");
                startSendFile();
            }
        }


        @Override
        public void onConnectError(WebSocket websocket, WebSocketException exception)
                throws Exception {
            super.onConnectError(websocket, exception);
            Log.e(TAG, "连接错误");
            setStatus(WsStatus.CONNECT_FAIL);
            reconnect();//连接错误的时候调用重连方法
        }


        @Override
        public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer)
                throws Exception {
            super.onDisconnected(websocket, serverCloseFrame, clientCloseFrame, closedByServer);
            Log.e(TAG, "断开连接");
            setStatus(WsStatus.CONNECT_FAIL);
            reconnect();//连接断开的时候调用重连方法
        }
    }



    private void setStatus(WsStatus status) {
        this.mStatus = status;
    }


    private WsStatus getStatus() {
        return mStatus;
    }


    public void disconnect() {
        if (ws != null) {
            ws.disconnect();
        }
    }


    private Handler mHandler = new Handler();
    private int reconnectCount = 0;//重连次数
    private long minInterval = 3000;//重连最小时间间隔
    private long maxInterval = 60000;//重连最大时间间隔


    public void reconnect() {
        if (!isNetConnect()) {
            reconnectCount = 0;
            Log.e(TAG, "重连失败网络不可用");
            setStop(true);
            return;
        }

        //这里其实应该还有个用户是否登录了的判断 因为当连接成功后我们需要发送用户信息到服务端进行校验
        //由于我们这里是个demo所以省略了
        if (ws != null &&
                !ws.isOpen() &&//当前连接断开了
                getStatus() != WsStatus.CONNECTING) {//不是正在重连状态

            reconnectCount++;
            setStatus(WsStatus.CONNECTING);

            long reconnectTime = minInterval;
            if (reconnectCount > 3) {
//                long temp = minInterval * (reconnectCount - 2);
//                reconnectTime = temp > maxInterval ? maxInterval : temp;
                //重连大于 3次之后不连接了
                Log.e(TAG, "重连失败网络不可用");
                setStop(true);
                return;
            }

            Log.e(TAG, "准备开始第"+reconnectCount+"次重连,重连间隔"+reconnectTime+" -- url:"+url );
            mHandler.postDelayed(mReconnectTask, reconnectTime);
        }
    }


    private Runnable mReconnectTask = new Runnable() {

        @Override
        public void run() {
            try {
                ws = new WebSocketFactory().createSocket(url, CONNECT_TIMEOUT)
                        .setFrameQueueSize(FRAME_QUEUE_SIZE)//设置帧队列最大值为5
                        .setMissingCloseFrameAllowed(false)//设置不允许服务端关闭连接却未发送关闭帧
                        .addListener(mListener = new WsListener())//添加回调监听
                        .connectAsynchronously();//异步连接
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };


    private void cancelReconnect() {
        reconnectCount = 0;
        mHandler.removeCallbacks(mReconnectTask);
    }

    //
    private Context context;
    private Boolean stopFlag = false;
    private String cacheId = "";
    private BreakpointInfo info = new BreakpointInfo();

    private HashMap params = new HashMap();
    private File file = null;
    /**
     * 初始化 断点续传的功能 ，传入 文件路径，默认进行文件分片发送文件，从第一个分片开始
     * @param context
     * @param path 需上传文件的路径
     * @param cacheId 缓存Id 并且传入 后台当做文件夹
     */
    public void initSendFile(Context context, final String path,String cacheId){
        this.ws= ws;
        this.context= context;
        this.cacheId = cacheId;
        this.stopFlag = false;
        info = new BreakpointInfo(path);
        info.setTempFile(context.getExternalCacheDir()+"/"+ cacheId );
        initParms();
    }

    /**
     * 初始化 断点续传的功能 ，使用 缓存的缓存数据进行初始化
     * @param info 缓存已上传的 文件相关信息
     * @param cacheId 缓存Id 并且传入 后台当做文件夹
     */
    public void initSendFile(Context context,BreakpointInfo info,String cacheId){
        this.context = context;
        this.cacheId = cacheId;
        this.info = info;
        stopFlag = false;
        initParms();
    }
    //初始化上传参数
    private HashMap initParms(  ){
        HashMap res = new HashMap();

        params.put("fileName",info.getFile().getName());
        params.put("start",info.getCurrentBlock());
        params.put("end",info.getBLOCK_SIZE());
        params.put("md5",info.getMd5Hsh());
        params.put("cacheId",cacheId);
        //文件分片
        file = SliceFile();
        res.put("params",params);
        res.put("file",file);
        return  res;

    }

    //分片文件
    private File SliceFile() {
        File tempfile = new File(info.getTempFile()+"/"+info.getCurrentBlock()+"-"+info.getFile().getName());
        FileInputStream in = null;
        OutputStream out = null;
        try {
            if ( !tempfile.exists() ){
                tempfile.createNewFile();
            }
            in = new FileInputStream(info.getFile());
            out = new FileOutputStream(tempfile);

            //跳到对应的 位置分片继续写入 临时文件
            long seekStart  = 0;
            if ( info.getCurrentBlock() != 0){
                seekStart = info.getCurrentBlock() *  BreakpointInfo.CHUNK_SIZE;
            }
            if (seekStart == -1) {
                seekStart = 0;
            }
            in.skip(seekStart);


            long seekEnd  = ( info.getCurrentBlock() +1 ) *  BreakpointInfo.CHUNK_SIZE;

            int len;
            byte[] buf = new byte[1024];
            while ((len = in.read(buf)) >= 0 && seekStart < seekEnd) {
                out.write(buf, 0, len);
                seekStart += len;
                if (seekStart + 1024 > seekEnd) {
                    buf = new byte[Integer.valueOf((seekEnd - seekStart) + "")];
                }
            }
            return tempfile;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {

            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    //发送 wesocket 请求
    public void startSendFile(){

        Log.e(TAG, "请求数据："+JSONObject.toJSONString(params));
        if (params != null && params.size() != 0){
            ws.sendText(JSONObject.toJSONString(params));
        }

    }



    //设置是否上传 标识
    public void setStop(boolean flag) {
        stopFlag = flag;
    }

    //获取上传 标识
    public boolean getStopFlag() {
        return stopFlag;
    }

    public BreakpointInfo getInfo() {
        return info;
    }

    private boolean isNetConnect() {
        ConnectivityManager connectivity = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivity != null) {
            NetworkInfo info = connectivity.getActiveNetworkInfo();
            if (info != null && info.isConnected()) {
                // 当前网络是连接的
                if (info.getState() == NetworkInfo.State.CONNECTED) {
                    // 当前所连接的网络可用
                    return true;
                }
            }
        }
        return false;
    }
}

